package cn.b504.qpcr.utils;

import cn.b504.qpcr.entity.AnalysisMethodSetting;
import cn.b504.qpcr.entity.Well;

import java.util.ArrayList;
import java.util.List;

/**
 * @author ssc
 * @apiNote update: 2020-06-08 21:20
 * @apiNote update: 2021-04-16
 */
public class CalCqUtils {
  public static final int cqMinLimit = 5;

//  版本1
//  public static boolean isError(List<Float> list) {
//    float min = Float.MAX_VALUE;
//    float max = -Float.MAX_VALUE;
//    for (Float aFloat : list) {
//      if (aFloat > max) {
//        max = aFloat;
//      }
//      if (aFloat < min) {
//        min = aFloat;
//      }
//    }
//    float sigLevel;
//    sigLevel = 5;
//    return (max - min < 1 + (sigLevel - 5) * 0.2);
//  }

  /* isError(笑林版本2)备份
   public static boolean isError(List<Float> list, float sigLevel) {
     float min = Float.MAX_VALUE;
     float max = -Float.MAX_VALUE;
     int minIndex = 0;
     int tempIndex = 0;
     for (Float aFloat : list) {
       if (aFloat < min) {
         minIndex = tempIndex;
         min = aFloat;
       }
       tempIndex++;
     }
     max = min;
     for (int i = minIndex; i < list.size(); i++) {
       if (list.get(i) > max) {
         max = list.get(i);
       }
     }
     if (min >= 10) {
       return ((max - min) / min < (0.1 * sigLevel / 3));
     } else {
       return (max - min < 1 + (sigLevel - 5) * 0.2);
     }
   }*/

//IsError备份,李老师提出的第一种解决方案0911/2022
/*  public static boolean isError(List<Float> list, float sigLevel) {
    List<Float> tempList = new ArrayList<>(list);
    List<Float> gradList = new ArrayList<>();
    float min = Float.MAX_VALUE;
    float max = -Float.MAX_VALUE;
    float gradMax = -Float.MAX_VALUE;
    int minIndex = 0;

    for (Float value : tempList) {
      if (value > max) {
        max = value;
      }
    }
    for (int i = 0; i < tempList.size() - 1; i++) {
      if (tempList.get(i) <= 0) {
        continue;
      }
      float tempValue = (tempList.get(i + 1) - tempList.get(i)) / max;
      gradList.add(tempValue);
    }
    for (Float aFloat : gradList) {
      if (aFloat > gradMax) {
        gradMax = aFloat;
      }
    }
    double thresholdGrad = 0.015 + (sigLevel - 5) * 0.002;
    return gradMax < thresholdGrad;
  }*/

//0911-2022-IsError重写
  /*public static boolean isError(List<Float> list, float sigLevel) {
    List<Float> gradList = new ArrayList<>(list);
    float min = Float.MAX_VALUE;
    float max = -Float.MAX_VALUE;
    float gradMax = -Float.MAX_VALUE;
    int maxIndex = 0;
    for (int i = gradList.size() - 2; i >= 0; i--) {
      if (gradList.get(i) <= 0) {
        gradList.set(i, gradList.get(i + 1));
      }
    }
    for (int i = 0; i < gradList.size() - 2; i++) {
      gradList.set(i, (gradList.get(i) + gradList.get(i + 1) + gradList.get(i + 2)) / 3);
    }
    for (Float aFloat : gradList) {
      if (aFloat > max) {
        max = aFloat;
      }
    }
    for (int i = 0; i < gradList.size() - 1; i++) {
      gradList.set(i, (gradList.get(i + 1) - gradList.get(i)) / max);
    }
    gradList.set(gradList.size() - 1, (gradList.get(gradList.size() - 2)));
    for (int i = 0; i < gradList.size(); i++) {
      if (gradList.get(i) > gradMax) {
        gradMax = gradList.get(i);
        maxIndex = i;
      }
    }
    if (maxIndex >= 2&& maxIndex < gradList.size()-3) {
      System.out.println("maxIndex = " + maxIndex + " " + "gradMax = " + gradMax);
      System.out.println(gradList.get(maxIndex - 2) + " ," + gradList.get(maxIndex - 1) + " , " + gradList.get(maxIndex) + "  , " + gradList.get(maxIndex + 1) + " ," + gradList.get(maxIndex + 2));
    }
    double thresholdGrad = 0.05 + (sigLevel - 5) * 0.01;
    return gradMax < thresholdGrad;
  }*/

  //0913最新iserror
/*  public static boolean isError(List<Float> list, float sigLevel) {
    List<Float> gradList = new ArrayList<>(list);
    float min = Float.MAX_VALUE;
    float max = -Float.MAX_VALUE;
    float gradMax = -Float.MAX_VALUE;
    int maxIndex = 0;
    for (int i = gradList.size() - 2; i >= 0; i--) {
      if (gradList.get(i) <= 0) {
        gradList.set(i, gradList.get(i + 1));
      }
    }
    for (int i = 0; i < gradList.size() - 2; i++) {
      gradList.set(i, (gradList.get(i) + gradList.get(i + 1) + gradList.get(i + 2)) / 3);
    }
    for (Float aFloat : gradList) {
      if (aFloat > max) {
        max = aFloat;
      }
    }
    for (int i = 0; i < gradList.size() - 1; i++) {
      gradList.set(i, (gradList.get(i + 1) - gradList.get(i)) / max);
    }
    gradList.set(gradList.size() - 1, (gradList.get(gradList.size() - 2)));
    for (int i = 0; i < gradList.size(); i++) {
      if (gradList.get(i) > gradMax) {
        gradMax = gradList.get(i);
        maxIndex = i;
      }
    }
    double thresholdGrad = 0.025 + (sigLevel - 5) * 0.006;
    return gradMax < thresholdGrad;
  }*/

  //0914加入cq判断
  public static boolean isError(List<Float> list, float sigLevel) {
    List<Float> gradList = new ArrayList<>(list);
    float min = Float.MAX_VALUE;
    int minIndex = 0;
    int cqInit = -1;
    float max = -Float.MAX_VALUE;
    float gradMax = -Float.MAX_VALUE;
    int maxIndex = 0;
    for (int i = 0; i < gradList.size() - 2; i++) {
      //平滑处理
      gradList.set(i, (gradList.get(i) + gradList.get(i + 1) + gradList.get(i + 2)) / 3);
    }
    for (int i = 0; i < gradList.size(); i++) {
      if (gradList.get(i) <= min) {
        min = gradList.get(i);
        minIndex = i;
      }
      if (gradList.get(i) > max) {
        max = gradList.get(i);
      }
    }
    if (minIndex >= gradList.size() - 2) {
      return true;
    }
    for (int i = 0; i < gradList.size() - 1; i++) {
      gradList.set(i, (gradList.get(i + 1) - gradList.get(i)) / max);
    }
    gradList.set(gradList.size() - 1, (gradList.get(gradList.size() - 2)));
    for (int i = minIndex; i < gradList.size(); i++) {
      if (gradList.get(i) > gradMax) {
        gradMax = gradList.get(i);
        maxIndex = i;
      }
    }
    if (maxIndex - minIndex < 2) {
      return true;
    }

    double thresholdGrad = 0.025 + (sigLevel - 5) * 0.006;
    return gradMax < thresholdGrad;
  }

  // 做均值滤波
  public static List<Float> averageFilter2NewList(List<Float> list) {
    List<Float> result = new ArrayList<>(list);
    averageFilter(result);
    return result;
  }

  // 做均值滤波，直接处理
  public static void averageFilter(List<Float> list) {
    if (list.size() < 2) {
      return;
    }
    list.set(0, MathUtils.average(list.get(0), list.get(0), list.get(1)));
    int lastSizeButOne = list.size() - 1;
    for (int i = 1; i < lastSizeButOne; i++) {
      list.set(i, MathUtils.average(list.get(i - 1), list.get(i), list.get(i + 1)));
    }
    list.set(lastSizeButOne, MathUtils.average(list.get(lastSizeButOne - 1), list.get(lastSizeButOne), list.get(lastSizeButOne)));
  }

  // 核心算法：做 S-G 滤波
  public static List<Float> sgFilter(List<Float> list) {
    List<Float> sg = new ArrayList<>();
    if (list.size() > 5) {
      int i = 0;
      // ahead
      while (i < list.size() - list.size() % 5 - 5) {
        addSG(sg, list.get(i), list.get(i + 1), list.get(i + 2), list.get(i + 3), list.get(i + 4), 5);
        i += 5;
      }
      // next but less 5
      addSG(sg, list.get(i), list.get(i + 1), list.get(i + 2), list.get(i + 3), list.get(i + 4), list.size() % 5);
      // last 5
      i = list.size() - 5;
      addSG(sg, list.get(i), list.get(i + 1), list.get(i + 2), list.get(i + 3), list.get(i + 4), 5);
      return sg;
    }
    if (list.size() == 5) {
      float y0 = list.get(0), y1 = list.get(1), y2 = list.get(2), y3 = list.get(3), y4 = list.get(4);
      addSG(sg, y0, y1, y2, y3, y4, 5);
      return sg;
    }
    return list;
  }

  private static void addSG(List<Float> sg, float y1, float y2, float y3, float y4, float y5, int length) {
    length = Math.min(length, 5);
    for (int i = 0; i < length; i++) {
      switch (i) {
        case 0:
          sg.add((9936 * y1 + 576 * y2 - 864 * y3 + 576 * y4 - 144 * y5) / 10080);
          break;
        case 1:
          sg.add((576 * y1 + 7776 * y2 + 3456 * y3 - 2304 * y4 + 576 * y5) / 10080);
          break;
        case 2:
          sg.add((-864 * y1 + 3456 * y2 + 4896 * y3 + 3456 * y4 - 864 * y5) / 10080);
          break;
        case 3:
          sg.add((576 * y1 - 2304 * y2 + 3456 * y3 + 7776 * y4 + 576 * y5) / 10080);
          break;
        case 4:
          sg.add((-144 * y1 + 576 * y2 - 864 * y3 + 576 * y4 + 9936 * y5) / 10080);
          break;
      }
    }
  }

  // 标准差计算
  private static float getStdevp(List<Float> list) {
    float total = 0, average = MathUtils.getAverageOfList(list, 0);
    for (float data : list) {
      total += Math.pow((data - average), 2);   //求出方差，如果要计算方差的话这一步就可以了
    }
    return (float) Math.sqrt(total / (list.size()));   //求出标准差
  }

  //  TODO:新标准差
  public static float getNewStdevp(List<Float> list) {
    float total = 0, average = MathUtils.getAverageOfList(list, 0);

    for (float data : list) {
      total += Math.pow((data - average), 2);   //求出方差，如果要计算方差的话这一步就可以了
    }

    return (float) Math.sqrt(total / (list.size()));   //求出标准差
  }

  // 10 倍标准差
  public static float getStdevp10FromRange(List<Float> list, int startCycle, int endCycle) {
    List<Float> dataListPart = new ArrayList<>();
    for (int i = Math.max(startCycle - 1, 0); i < Math.min(endCycle, list.size()); i++) {
      dataListPart.add(list.get(i));
    }
    return getStdevp(dataListPart) * 10;
  }

  // 通过三阶导数求Cq
  public static float getCqByMaximum(List<Float> list) {
    List<Float> dao1 = MathUtils.getDaoList_jumpOne(list),
        dao2 = MathUtils.getDaoList_jumpOne(dao1);

    int max1 = MathUtils.maxIndexSinceIndex3(dao1),
        max2 = MathUtils.maxIndexSinceIndex3(dao2, max1 + 1);
    float a = dao2.get(max2),
        b = dao2.get(Math.max(max2 - 1, 0)),
        c = dao2.get(Math.min(max2 + 1, dao2.size() - 1)),
        d = b - c < 0 ? b : c,
        delta = (b - c) / (a - d) * 0.5F;
    return max2 + 1 - delta;
  }

  //  计算终止循环的Cq
  public static float getCqByEarlyMaximum(List<Float> list) {
    List<Float> dao1 = MathUtils.getDaoList_jumpOne(list);
    float max = dao1.get(0);
    float index = 0;
    for (int i = 0; i < dao1.size(); i++) {
      if (dao1.get(i) > max) {
        max = dao1.get(i);
        index = (float) i;
      }
    }
    return index;
  }

  public static boolean shouldSkip(Well well, final String targetName, short channelIndex) {
    //  TODO:增加了channelIndex < 0
    return channelIndex < 0 || well.getLines()[channelIndex].getOmit() || well.getTargets().stream().noneMatch(it -> it.getName().equals(targetName));
  }


  public static List<Float> sgFilterList(Well well, final String targetName, short channelIndex, String method) {
    float sigLevel = well.getLines()[channelIndex].getSigLevel();
    if (CalCqUtils.shouldSkip(well, targetName, channelIndex)) {
      return null;
    }
    List<Float> list = CalCqUtils.getData(well.getLines()[channelIndex], method);
    List<Float> dataList = CalCqUtils.sgFilter(MathUtils.backgroundRemoval(list));
//    if (CalCqUtils.isError(dataList)) {
//      return null;
//    }

    List<Float> originList = CalCqUtils.getData(well.getLines()[channelIndex], AnalysisMethodSetting.Method.RAW_DATA);
    if (CalCqUtils.isError(originList, sigLevel)) {
      return null;
    }
    return list;
  }

  public static List<Float> newSgFilterList(Well well, final String targetName, short channelIndex, String method) {
    float sigLevel = well.getLines()[channelIndex].getSigLevel();
    if (CalCqUtils.shouldSkip(well, targetName, channelIndex)) {
      return null;
    }
    List<Float> list = CalCqUtils.getData(well.getLines()[channelIndex], method);
    List<Float> dataList = CalCqUtils.sgFilter(MathUtils.backgroundRemoval(list));
//    if (CalCqUtils.isError(dataList)) {
//      return null;
//    }

    List<Float> originList = CalCqUtils.getData(well.getLines()[channelIndex], AnalysisMethodSetting.Method.RAW_DATA);
    if (CalCqUtils.isError(originList, sigLevel)) {
      return null;
    }

    return dataList;
  }

  //TODO:
  public static float calByRegression(List<Float> originList, List<Float> list, int startCycle, int endCycle, float sigLevel) {
//    float sigLevel = line.getSigLevel();
    if (CalCqUtils.isError(originList, sigLevel)) {
      return -1F;
    }
    startCycle = Math.max(0, startCycle);
    endCycle = Math.min(list.size(), endCycle);
    float stdevp10 = CalCqUtils.getStdevp10FromRange(list, startCycle, endCycle);
    return MathUtils.y2x(list, stdevp10);
  }

  public static float calByExtremum(List<Float> originList, List<Float> list, float sigLevel) {
    if (CalCqUtils.isError(originList, sigLevel)) {
      return -1F;
    }
    float cqValue = CalCqUtils.getCqByMaximum(list);
    if (cqValue < CalCqUtils.cqMinLimit || list.size() < cqValue) {
      return -1F;
    }
    float y = MathUtils.x2y(list, cqValue);
    for (int j = list.size() - 1; j > 0; j--) {
      float r = list.get(j), l = list.get(j - 1);
      if (l <= y && y <= r) {
        return j - (y - r) / (l - r);
      }
    }
    return -1F;
  }

  public static float calByEarlyExtremum(List<Float> list) {
    return CalCqUtils.getCqByEarlyMaximum(list);
  }

  public static List<Float> getData(Well.Line line, String method) {
    switch (method) {
      case AnalysisMethodSetting.Method.BASELINE_SUBTRACTED_CURVE_FIT:
        return line.getQuantificationCurveFitter();
      case AnalysisMethodSetting.Method.BASELINE_CORRECTION:
        return line.getQuantificationBaseline();
      case AnalysisMethodSetting.Method.RAW_DATA:
        return line.getQuantificationOpt();
      default:
        return line.getQuantificationOrigin();
    }
  }
}
